#include<iostream>
#include<cstdlib>
#include<unistd.h>
#include<errno.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
#include<string>

#define ENV_MAX 64
char* g_env[ENV_MAX];

extern char** environ;//总是指向bash的环境表, 在系统启动的时候就导入了
void init_env()
{
    int i = 0;
    for(; environ[i]; ++i)
    {
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1);//+1是为了存'\n', strlen不会记录'\0'
        memcpy(g_env[i], environ[i], sizeof(g_env[i]));
    }  
    g_env[i] = nullptr;//最后一位必须是nullptr
}


//否则常量字符串"NONE"返回时会权限放大
const char* get_USER()
{
    char* name = getenv("USER");
    return name == nullptr ? "NONE" : name;
}


//不知道为什么 取不出来HOSTNAME
const char* get_HOSTNAME()
{
    char* hostname = getenv("HOSTNAME");
    return hostname == nullptr ? "NONE" : hostname;
}

// const char* get_HOSTNAME()
// {
//     char hostname[255];
//     int get_ret = gethostname(hostname, sizeof(hostname));

//     if (get_ret == -1)
//         return "NONE";
//     else
//         return hostname;
// }

#define CWD_MAX 1024
char cwd[CWD_MAX];
char cwd_env[CWD_MAX];

const char* get_PWD()//工作目录先变化, shell再自己自动更新环境变量PATH -> 所以MyShell要手动实现该过程
{

    char* ret = getcwd(cwd, sizeof(cwd));
    if(ret == nullptr)
        exit(errno);
    
    //更新环境变量: 以"xx=xxx"的格式
    snprintf(cwd_env, sizeof(cwd_env), "PWD=%s", cwd);
    putenv(cwd_env);
    
    return cwd;
}

const char* get_HOME()
{
    char* home = getenv("HOME");
    return home;
}


//1.输出命令行提示字符串
void print_command_prompt()
{
//提示格式
#define PROMPT_FORMAT "[%s@%s:%s]$ "

    printf(PROMPT_FORMAT, (const char*)get_USER(), (const char*)get_HOSTNAME(), (const char*)get_PWD());
    fflush(stdout);//刷新缓冲区到标准输出流(屏幕) -- 保险工作: 确保刷新成功
}



//2.获取用户输入的指令
bool get_user_command(char* command_line, size_t str_max_size)
{
    char* ret = fgets(command_line, str_max_size, stdin);//网站看函数使用
    if(ret == nullptr)
        exit(errno);
    command_line[strlen(command_line) - 1] = '\0';//清除最后的'\n', 不用考虑越界 因为fget函数至少读到一个'\n'
    if(strlen(command_line) == 0)
        return false;
    return true;
}


//全局变量
#define ARGC_MAX 64
int g_argc;
char* g_argv[ARGC_MAX];


//3.解析指令(切分命令 形成命令行参数表)
bool command_parse(char* command_line)
{
#define DELIMITER " "

    g_argc = 0;
    g_argv[g_argc++] = strtok(command_line, DELIMITER);//分隔符: char* -> 双引号" "
    //每次只能分割一下, 后续用nullptr代表从上次分割的位置继续分割
    while( (g_argv[g_argc++] = strtok(nullptr, DELIMITER)) != nullptr);

    g_argv[g_argc] == nullptr;//最后一个元素必须置为空
    g_argc--;//修正 -- 最后一次 strtok从'\0'开始分隔导致分割失败 但g_argc也会++

    return g_argc > 0 ? true : false;//加层保险
}


int last_exit_code = 0;//用于echo $?
 

//4.检测并处理内建命令(bash自己执行 -- 本质: bash调用自己的函数)
bool check_and_execute_command_bulitin()
{
    //cd
    if(strcmp(g_argv[0], "cd") == 0)
    {
        int ch_ret = 0;
        if(g_argc == 1) //"cd" -> 返回家目录
            ch_ret = chdir(get_HOME()); //改变当前进程的工作目录 -- 工作目录先变化, shell再自己更新环境变量PATH
        
        else if (g_argc == 2) //"cd -" "cd ~"等特殊命令暂不实现
            ch_ret = chdir(g_argv[1]);

        if(g_argc > 2 || ch_ret == -1)
            printf(">>cd命令参数输入有误, 重新输入!!\n");

        return true;
    }

    //echo
    else if(strcmp(g_argv[0], "echo") == 0)
    {
        //只实现"echo $?", "echo $env", "echo XXX"
        if(g_argc == 2)
        {
            if(g_argv[1][0] == '$')
            {
                if(g_argv[1][1] == '?')
                {
                    printf("%d\n", last_exit_code);
                    last_exit_code = 0;
                }
                else
                {
                    std::string str(g_argv[1]);
                    std::string option = str.substr(1);
                    char* env = getenv(option.c_str());
                    if(env)
                        printf("%s\n", env);

                    else
                        std::cout << ">>功能暂未实现哦~~" << std::endl;
                    
                }
            }
            else
            {
                std::cout << g_argv[1] << std::endl;
            }
            return true;//子进程也能实现echo, bash实现了两份echo
        }
    }

    //pwd,export,env......


    return false;
}


//5.执行外部命令(交给子进程执行)
void execute_command()
{
    pid_t id = fork();
    if(id == -1)
        exit(errno);

    if(id == 0) //child
    {
        execvp(g_argv[0], g_argv);
    }
    
    //father 
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status))
            last_exit_code = WEXITSTATUS(status);
    }
} 

void print_argv()
{
    for(int i = 0; i < g_argc; ++i)
    {
        std::cout << g_argv[i] << std::endl;
    }
    printf("%d\n", g_argc);
}


int main()
{
    //0.从配置文件中读取出环境变量表
    //我实现MyShell的env表是从父进程Shell继承而来的, 这里就不实现导入环境变量了
    init_env();


    while(1)
    {
        //1.输出命令行提示字符串
        print_command_prompt();

        //2.获取用户输入的指令
#define COMMAND_MAX_SIZE 1024

        char command_line[COMMAND_MAX_SIZE];
        if(get_user_command(command_line, sizeof(command_line)) == false)
            continue;


        //3.解析指令(切分命令 形成命令行参数表)
        if((command_parse(command_line)) == false)
            continue;
        //print_argv();


        //4.检测并处理内建命令(bash自己执行)
        if((check_and_execute_command_bulitin()) == true)
            continue;

        //5.执行外部命令(交给子进程执行)
        execute_command();
        
    }

    return 0;
}